---
title: Actions
description: 学习如何创建类型安全的服务器函数，你可以从任何地方调用它们。
i18nReady: true
---

import { Steps } from '@astrojs/starlight/components';
import Since from '~/components/Since.astro';
import ReadMore from '~/components/ReadMore.astro';

<p><Since v="4.15" /></p>

Astro Actions 允许你定义和调用具有类型安全性的后端函数。Actions 为你执行数据请求、JSON 解析和输入验证。与使用 [API 端点](/zh-cn/guides/endpoints/) 相比，这可以大大减少所需的样板代码量。

使用 actions 而不是 API 端点，以实现客户端和服务器代码之间的无缝通信，并且可以：

- 使用 [Zod](https://zod.dev/?id=primitives) 自动校验 JSON 和表单数据输入。
- 生成类型安全的函数，以便从客户端调用后端，甚至可以[从 HTML 表单操作](#从-html-表单操作调用-action)中调用。无需手动 `fetch()` 调用。
- 使用 [`ActionError`](/zh-cn/reference/modules/astro-actions/#actionerror) 对象标准化后端错误。

## 基本用法

Actions 是在 `src/actions/index.ts` 中导出的 `server` 对象中定义的：

```ts title="src/actions/index.ts"
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  myAction: defineAction({ /* ... */ })
}
```

Actions 作为函数从 `astro:actions` 模块中导入。导入 `actions` 并在 [UI 框架组件](/zh-cn/guides/framework-components/)、[表单 POST 请求](#从-html-表单操作调用-action) 等客户端中使用，或在 Astro 组件中的 `<script>` 标签中调用它们。

当你调用一个 action 时，它会返回一个对象，其中 `data` 包含 JSON 序列化的结果，或 `error` 包含抛出的错误。

```astro title="src/pages/index.astro"
---
---

<script>
import { actions } from 'astro:actions';

async () => {
  const { data, error } = await actions.myAction({ /* ... */ });
}
</script>
```

### 编写你的第一个 action

按照下面的步骤定义一个 action 并在 Astro 页面的 `script` 标签中调用它。

<Steps>

1. 创建一个 `src/actions/index.ts` 文件并导出一个 `server` 对象。

    ```ts title="src/actions/index.ts"
    export const server = {
      // action 声明
    }
    ```

2. 导入 `astro:actions` 中的 `defineAction()` 工具以及 `astro:schema` 中的 `z` 对象。

    ```ts ins={1-2} title="src/actions/index.ts"
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';

    export const server = {
      // action 声明
    }
    ```

3. 使用 `defineAction()` 工具定义一个 `getGreeting` action。`input` 属性将使用 [Zod](https://zod.dev) scheme 验证输入参数，`handler()` 函数包含要在服务器上运行的后端逻辑。

    ```ts ins={5-12} title="src/actions/index.ts"
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';

    export const server = {
      getGreeting: defineAction({
        input: z.object({
          name: z.string(),
        }),
        handler: async (input) => {
          return `你好，${input.name}!`
        }
      })
    }
    ```

4. 创建一个 Astro 组件，其中包含一个按钮，当点击时将使用 `getGreeting` action 获取问候语。

    ```astro title="src/pages/index.astro"
    ---
    ---

    <button>获取问候语</button>

    <script>
    const button = document.querySelector('button');
    button?.addEventListener('click', async () => {
      // 通过 action 弹出带有问候语的弹窗
    });
    </script>
    ```

5. 要使用 action，请从 `astro:actions` 导入 `actions`，然后在单击处理程序中调用 `actions.getGreeting()`。`name` 选项将被发送到服务器上的 action 的 `handler()`，如果没有报错，你可以从 `data` 属性上获取到执行结果。

    ```astro title="src/pages/index.astro" ins={7, 12-13}
    ---
    ---

    <button>获取问候语</button>

    <script>
    import { actions } from 'astro:actions';

    const button = document.querySelector('button');
    button?.addEventListener('click', async () => {
      // 通过 action 弹出带有问候语的弹窗
      const { data, error } = await actions.getGreeting({ name: "Houston" });
      if (!error) alert(data);
    })
    </script>
    ```

</Steps>

<ReadMore>有关 [`defineAction()`](/zh-cn/reference/modules/astro-actions/#defineaction) 及其属性的详细信息，请参阅完整的 Actions API 文档。</ReadMore>

## 组织 actions

在你的项目中，所有的 actions 都必须从 `src/actions/index.ts` 文件中的 `server` 对象中导出。你可以内联定义 actions，也可以将 action 定义移动到单独的文件中并导入它们。你甚至可以将相关函数分组到嵌套对象中。

例如，要将所有用户 actions 放在一起，你可以创建一个 `src/actions/user.ts` 文件，并将 `getUser` 和 `createUser` 的定义嵌套在一个 `user` 对象中。

```ts title="src/actions/user.ts"
import { defineAction } from 'astro:actions';

export const user = {
  getUser: defineAction(/* ... */),
  createUser: defineAction(/* ... */),
}
```

然后，你可以将此 `user` 对象导入到你的 `src/actions/index.ts` 文件中，并将其作为顶层键与任何其他 actions 一起添加到 `server` 对象中：

```ts title="src/actions/index.ts" ins={1,5}
import { user } from './user';

export const server = {
  myAction: defineAction({ /* ... */ }),
  user,
}
```

现在，你可以从 `actions.user` 对象中调用所有用户 actions：

- `actions.user.getUser()`
- `actions.user.createUser()`


## 处理返回的数据

Actions 会返回一个对象，这个对象要么是具备 `handler()` 类型安全性的返回值 `data`，要么是包含着任何后端错误的 `error`。错误可能来自 `input` 属性上的验证错误，或者来自 `handler()` 中抛出的错误。

Actions 可以返回 [使用 Devalue 库](https://github.com/Rich-Harris/devalue) 处理的自定义数据，如日期，Maps, Sets 和 URL。这意味着你不能像处理常规 JSON 一样轻松地检查网络响应。为了调试，你可以检查 actions 返回的 `data` 对象。

<ReadMore>有关完整信息，请参阅 [`handler()` API 参考](/zh-cn/reference/modules/astro-actions/#handler-属性)。</ReadMore>

###  检查错误

最好在使用 `data` 属性之前检查是否存在 `error`。这样可以提前处理错误，并确保 `data` 在没有 `undefined` 检查的情况下被定义。

```ts
const { data, error } = await actions.example();

if (error) {
  // 处理错误
  return;
}
// 使用 `data`
```

### 直接访问 `data` 而不检查错误

为了跳过错误处理，例如在原型设计或使用将为你捕获错误的库时，可以在调用 action 时使用 `.orThrow()` 属性来抛出错误，而不是返回一个 `error`。这将直接返回 action 的 `data`。

这个例子调用了一个 `likePost()` action，它从 action `handler` 返回一个 `number` 类型的更新后的点赞数：

```ts ins="orThrow"
const updatedLikes = await actions.likePost.orThrow({ postId: 'example' });
//    ^ type：number
```

### 在 action 中处理后端错误

你可以使用提供的 `ActionError` 从你的 action `handler()` 中抛出错误，例如当数据库条目丢失时抛出“未找到”，或者当用户未登录时抛出“未经授权”。这比返回 `undefined` 有两个主要好处：

- 你可以设置一个状态码，例如 `404 - 未找到` 或 `401 - 未经授权`。这可以让你通过查看每个请求的状态码来改善开发和生产中错误调试的体验。

- 在你的应用程序代码中，所有错误都会传递到 action 结果上的 `error` 对象。这避免了对数据进行 `undefined` 检查的需要，并允许你根据出现的问题来向用户展示更针对性的反馈。

#### 创建一个 `ActionError`

为了抛出一个错误，从 `astro:actions` 模块中导入 `ActionError()` 类。传递一个人类可读的状态 `code`（例如 `"NOT_FOUND"` 或 `"BAD_REQUEST"`），以及一个可选的 `message` 以提供有关错误的更多信息。

当用户并未登录，且在检查了假设的 “use-session” cookie 进行身份验证后，这个例子从 `likePost` action 抛出一个错误：

```ts title="src/actions/index.ts" ins=/ActionError(?= )/ ins={9-12}
import { defineAction, ActionError } from "astro:actions";
import { z } from "astro:schema";

export const server = {
  likePost: defineAction({
    input: z.object({ postId: z.string() }),
    handler: async (input, ctx) => {
      if (!ctx.cookies.has('user-session')) {
        throw new ActionError({
          code: "UNAUTHORIZED",
          message: "User must be logged in.",
        });
      }
      // 否则，点赞成功
    },
  }),
};
```

#### 处理 `ActionError`

为了处理错误，你可以从你的应用程序中调用 action 并检查是否存在 `error` 属性。这个属性将是 `ActionError` 类型，并将包含你的 `code` 和 `message`。

在下面的例子中，一个 `LikeButton.tsx` 组件在点击时调用 `likePost()` action。如果发生身份验证错误，`error.code` 属性用于确定是否显示登录链接：

```tsx title=src/components/LikeButton.tsx ins="if (error?.code === 'UNAUTHORIZED') setShowLogin(true);"
import { actions } from 'astro:actions';
import { useState } from 'preact/hooks';

export function LikeButton({ postId }: { postId: string }) {
  const [showLogin, setShowLogin] = useState(false);
  return (
    <>
      {
        showLogin && <a href="/signin">登录后点赞文章。</a>
      }
      <button onClick={async () => {
        const { data, error } = await actions.likePost({ postId });
        if (error?.code === 'UNAUTHORIZED') setShowLogin(true);
        // 因意外错误而提前终止
        else if (error) return;
        // 更新点赞数
      }}>
        点赞
      </button>
    </>
  )
}
```

### 处理客户端重定向

当在客户端调用 actions 时，你可以集成一个客户端库，例如 `react-router`，或者你可以使用 Astro 的 [`navigate()` 函数](/zh-cn/guides/view-transitions/#触发导航) 来在 action 成功时重定向到一个新页面。

这个例子在 `logout` action 成功返回后导航到主页：

```tsx title=src/pages/LogoutButton.tsx {2,7-8}
import { actions } from 'astro:actions';
import { navigate } from 'astro:transitions/client';

export function LogoutButton() {
  return (
    <button onClick={async () => {
      const { error } = await actions.logout();
      if (!error) navigate('/');
    }}>
      退出登录
    </button>
  );
}
```

## 从 action 接受表单数据

Actions 默认接受 JSON 数据。要接受来自 HTML 表单的表单数据，请在 `defineAction()` 调用中设置 `accept: 'form'`：

```ts title="src/actions/index.ts" ins={6}
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  comment: defineAction({
    accept: 'form',
    input: z.object(/* ... */),
    handler: async (input) => { /* ... */ },
  })
}
```

### 校验表单数据

Actions 将解析提交的表单数据为一个对象，使用每个输入的 `name` 属性的值作为对象键。例如，包含 `<input name="search">` 的表单将被解析为一个对象，如 `{ search: 'user input' }`。你的 action 的 `input` 模式将用于验证此对象。

为了接收原始的 `FormData` 对象，而不是解析后的对象，可以在 action 定义中省略 `input` 属性。

下面的示例显示了一个验证过的 newsletter 注册表单，它接受用户的电子邮件并要求勾选“服务条款”复选框以同意。

<Steps>

1. 创建一个 HTML 表单组件，为每个输入设置唯一的 `name` 属性：

    ```astro title="src/components/Newsletter.astro" /name="\w+"/
    <form>
      <label for="email">E-mail</label>
      <input id="email" required type="email" name="email" />
      <label>
        <input required type="checkbox" name="terms">
        我已同意服务条款
      </label>
      <button>注册</button>
    </form>
    ```

2. 定义一个 `newsletter` action 来处理提交的表单。使用 `z.string().email()` 验证器验证 `email` 字段，使用 `z.boolean()` 验证器验证 `terms` 复选框：

    ```ts title="src/actions/index.ts" ins={5-12}
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';

    export const server = {
      newsletter: defineAction({
        accept: 'form',
        input: z.object({
          email: z.string().email(),
          terms: z.boolean(),
        }),
        handler: async ({ email, terms }) => { /* ... */ },
      })
    }
    ```

    <ReadMore>请参阅 [`input` 验证器](/zh-cn/reference/modules/astro-actions/#input-验证器) 查看所有可用的校验器</ReadMore>

3. 添加一个 `<script>` 到 HTML 表单中以提交用户输入。这个例子覆盖了表单的默认提交行为，调用 `actions.newsletter()`，并使用 `navigate()` 函数重定向到 `/confirmation`：

    ```astro title=src/components/Newsletter.astro ins={11-22} collapse={2-8}
    <form>
      <label for="email">E-mail</label>
      <input id="email" required type="email" name="email" />
      <label>
        <input required type="checkbox" name="terms">
        我已同意服务条款
      </label>
      <button>注册</button>
    </form>

    <script>
      import { actions } from 'astro:actions';
      import { navigate } from 'astro:transitions/client';

      const form = document.querySelector('form');
      form?.addEventListener('submit', async (event) => {
        event.preventDefault();
        const formData = new FormData(form);
        const { error } = await actions.newsletter(formData);
        if (!error) navigate('/confirmation');
      })
    </script>
    ```

    <ReadMore>请参阅 [“从 HTML 表单操作调用 action”](#从-html-表单操作调用-action) 以了解提交表单数据的另一种方法。</ReadMore>

</Steps>

### 展示表单输入错误

你可以在提交前校验表单输入，使用原生 HTML 表单校验属性，例如 `required`、`type="email"` 和 `pattern`。对于后端的更复杂的 `input` 校验，你可以使用 [`isInputError()`](/zh-cn/reference/modules/astro-actions/#isinputerror) 工具函数。

要检索输入错误，可以使用 `isInputError()` 工具函数来检查错误是否是由无效输入而引起的。输入错误包含一个 `fields` 对象，其中包含每个验证失败的输入名称的消息。你可以使用这些消息提示用户以纠正他们的提交。

下面的示例使用 `isInputError()` 检查错误，然后检查错误是否在电子邮件字段中，最后从错误中创建消息。你可以使用 JavaScript DOM 操作或你喜欢的 UI 框架将此消息显示给用户。

```js /isInputError(?= )/ {5-12}
import { actions, isInputError } from 'astro:actions';

const form = document.querySelector('form');
const formData = new FormData(form);
const { error } = await actions.newsletter(formData);
if (isInputError(error)) {
  // 处理输入错误。
  if (error.fields.email) {
    const message = error.fields.email.join(', ');
  }
}
```

## 从 HTML 表单操作调用 action

:::note
页面必须在使用表单操作调用 actions 时进行按需渲染。在使用此 API 之前，请[确保页面上已禁用预渲染](/zh-cn/guides/on-demand-rendering/)。
:::

你可以在任何 `<form>` 元素上使用标准属性启用零 JS 表单提交。无论是作为 JavaScript 加载失败时的回退，又或者你更喜欢仅从服务器来处理表单时，无需客户端 JavaScript 的表单提交都非常有用。

在服务器上调用 [Astro.getActionResult()](/zh-cn/reference/api-reference/#getactionresult) 会返回表单提交的结果（`data` 或 `error`），并且可以用于动态重定向、处理表单错误、更新 UI 等。

要从 HTML 表单调用 action，可以为 `<form>` 添加 `method="POST"`，并设置表单的 `action` 属性使用你的 action，例如 `action={actions.logout}`。这会将 `action` 属性设置为使用服务器自动处理的查询字符串。

例如，这个 Astro 组件在点击按钮时调用 `logout` action 并重新加载当前页面：

```astro title="src/components/LogoutButton.astro"
---
import { actions } from 'astro:actions';
---

<form method="POST" action={actions.logout}>
  <button>退出登录</button>
</form>
```

可能需要在 `<form>` 元素上添加额外的属性以实现 Zod 模式（Schema）的正确验证。例如，当包含文件上传功能时，必须添加 `enctype="multipart/form-data"` 属性，以保证文件数据以能被 z.instanceof(File) 正确识别的格式传输。

```astro title="src/components/FileUploadForm.astro"
---
import { actions } from 'astro:actions';
---
<form method="POST" action={actions.upload} enctype="multipart/form-data" >
  <label for="file">上传文件</label>
  <input type="file" id="file" name="file" />
  <button type="submit">提交</button>
</form>
```

### 在 action 成功时重定向

如果你需要在成功时重定向到一个新的路由，你可以在服务器上使用 action 的结果。一个常见的例子是创建一个产品记录并重定向到新产品的页面，例如 `/products/[id]`。

例如，假设你有一个 `createProduct` action，它返回生成的产品 id：

```ts title="src/actions/index.ts" mark={10}
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  createProduct: defineAction({
    accept: 'form',
    input: z.object({ /* ... */ }),
    handler: async (input) => {
      const product = await persistToDatabase(input);
      return { id: product.id };
    },
  })
}
```

你可以通过调用 `Astro.getActionResult()` 从你的 Astro 组件中检索 action 结果。当调用 action 时，它返回一个包含 `data` 或 `error` 属性的对象，或者如果在此请求期间未调用 action，则返回 `undefined`。

使用 `data` 属性构建一个 URL，然后使用 `Astro.redirect()`：

```astro title="src/pages/products/create.astro" {4-7}
---
import { actions } from 'astro:actions';

const result = Astro.getActionResult(actions.createProduct);
if (result && !result.error) {
  return Astro.redirect(`/products/${result.data.id}`);
}
---

<form method="POST" action={actions.createProduct}>
  <!--...-->
</form>
```

### 处理表单 action 错误

在 Astro 组件中调用 `Astro.getActionResult()`，可以让你访问 `data` 和 `error` 对象，以进行自定义错误处理。

下面的例子在 `newsletter` action 失败时显示一个通用的失败消息：

```astro title="src/pages/index.astro" {4,7-9}
---
import { actions } from 'astro:actions';

const result = Astro.getActionResult(actions.newsletter);
---

{result?.error && (
  <p class="error">无法注册。请稍后再试。</p>
)}
<form method="POST" action={actions.newsletter}>
  <label>
    E-mail
    <input required type="email" name="email" />
  </label>
  <button>注册</button>
</form>
```

为了更多的自定义，你可以[使用 `isInputError()` 工具](#展示表单输入错误)来检查错误是否由无效输入引起。

下面的例子在提交无效的电子邮件时，在 `email` 输入字段下方呈现一个错误横幅：

```astro title="src/pages/index.astro" ins={5,13} ins='aria-describedby="error"'
---
import { actions, isInputError } from 'astro:actions';

const result = Astro.getActionResult(actions.newsletter);
const inputErrors = isInputError(result?.error) ? result.error.fields : {};
---

<form method="POST" action={actions.newsletter}>
  <label>
    E-mail
    <input required type="email" name="email" aria-describedby="error" />
  </label>
  {inputErrors.email && <p id="error">{inputErrors.email.join(',')}</p>}
  <button>注册</button>
</form>
```

#### 在错误时保留输入值

输入框在提交表单时会被清空。为了保留输入值，你可以[在页面上启用视图过渡](/zh-cn/guides/view-transitions/#启用视图过渡动画spa-模式)，并对每个输入应用 `transition:persist` 指令：

```astro ins="transition:persist"
<input transition:persist required type="email" name="email" />
```

### 使用表单的 action 结果以更新 UI

要使用 action 的返回值在成功时向用户显示通知，将 action 传递给 `Astro.getActionResult()`。使用返回的 `data` 属性来渲染你想要显示的 UI。

这个例子使用 `addToCart` action 返回的 `productName` 属性来显示一个成功消息：

```astro title="src/pages/products/[slug].astro"
---
import { actions } from 'astro:actions';

const result = Astro.getActionResult(actions.addToCart);
---

{result && !result.error && (
  <p class="success">添加 {result.data.productName} 到购物车</p>
)}

<!--...-->
```

### 高级：通过 session 持久化 action 结果

<p><Since v="5.0.0" /></p>

Action 的结果显示为 POST 提交。这意味着当用户关闭并重新访问页面时，结果将重置为 `undefined`。如果用户尝试刷新页面，他们还将看到一个 "确认重新提交表单？" 对话框。

要自定义此行为，你可以添加中间件来手动处理 action 结果。你可以选择使用 cookie 或会话存储来持久化 action 结果。

首先 [创建一个中间件文件](/zh-cn/guides/middleware/)，并从 `astro:actions` 导入 [`getActionContext()` 工具](/zh-cn/reference/modules/astro-actions/#getactioncontext)。这个函数返回一个 `action` 对象，其中包含有关传入 action 请求的信息，包括 action 处理程序以及 action 是否是从 HTML 表单调用的。`getActionContext()` 还返回 `setActionResult()` 和 `serializeActionResult()` 函数，用于以编程方式设置 `Astro.getActionResult()` 返回的值：

```ts title="src/middleware.ts" {2,5}
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';

export const onRequest = defineMiddleware(async (context, next) => {
  const { action, setActionResult, serializeActionResult } = getActionContext(context);
  if (action?.calledFrom === 'form') {
    const result = await action.handler();
    // ... 处理 action 结果
    setActionResult(action.name, serializeActionResult(result));
  }
  return next();
});
```

保存 HTML 表单结果的常见做法是 [POST / Redirect / GET 模式](https://en.wikipedia.org/wiki/Post/Redirect/Get)。这个重定向会在页面刷新时移除 "确认重新提交表单？" 对话框，并允许 action 结果在用户会话期间持久化。

此示例使用安装了 [Netlify 服务器适配器](/zh-cn/guides/integrations-guide/netlify/) 的 session 存储，将 POST / 重定向 / GET 模式应用于所有表单提交。使用 [Netlify Blob](https://docs.netlify.com/blobs/overview/) 将 action 结果写入 session 存储，并在重定向后使用 session ID 检索：

```ts title="src/middleware.ts"
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';
import { randomUUID } from "node:crypto";
import { getStore } from "@netlify/blobs";

export const onRequest = defineMiddleware(async (context, next) => {
  // 跳过预渲染页面的请求
  if (context.isPrerendered) return next();
  
  const { action, setActionResult, serializeActionResult } =
    getActionContext(context);
  // 创建 Blob 存储以使用 Netlify Blob 保存 action 结果
  const actionStore = getStore("action-session");
  
  // 如果 action 结果作为 cookie 转发，则将 result
  // 设置为可从 `Astro.getActionResult()` 访问
  const sessionId = context.cookies.get("action-session-id")?.value;
  const session = sessionId
    ? await actionStore.get(sessionId, {
        type: "json",
      })
    : undefined;
  
  if (session) {
    setActionResult(session.actionName, session.actionResult);
  
    // 可选：页面渲染后删除 session。
    // 随意实现你自己的持久化策略
    await actionStore.delete(sessionId);
    context.cookies.delete("action-session-id");
    return next();
  }
  
  // 如果从 HTML 表单操作调用 action，
  // 调用 action handler 并重定向到目标页面
  if (action?.calledFrom === "form") {
    const actionResult = await action.handler();
  
    // 使用 session 存储保存 action 结果
    const sessionId = randomUUID();
    await actionStore.setJSON(sessionId, {
      actionName: action.name,
      actionResult: serializeActionResult(actionResult),
    });
  
    // 将 session ID 作为 cookie 传递
    // 重定向到页面后检索
    context.cookies.set("action-session-id", sessionId);
  
    // 出错时重定向回上一页
    if (actionResult.error) {
      const referer = context.request.headers.get("Referer");
      if (!referer) {
        throw new Error(
          "Internal: Referer unexpectedly missing from Action POST request.",
        );
      }
      return context.redirect(referer);
    }
    // 成功则跳转到目标页面
    return context.redirect(context.originPathname);
  }
  
  return next();
});
```

## 使用 actions 时的安全性

可以根据 action 的名称作为公共端点访问 Action 。例如，action `blog.like()` 将可以从 `/_actions/blog.like` 访问。这对于单元测试 action 结果和调试生产错误非常有用。然而，这意味着你**必须**使用与 API 端点和按需渲染页面相同的授权检查。

### 从 action handler 授权用户

要授权 action 请求，请在 action handler 中添加身份验证检查。你可能希望使用 [身份验证库](/zh-cn/guides/authentication/) 来处理会话管理和用户信息。

Action 公开完整的 APIContext 对象，以访问从中间件传递的属性，使用 `context.locals`。当用户未经授权时，你可以使用 `UNAUTHORIZED` 代码引发 `ActionError`：

```ts title="src/actions/index.ts" {6-8}
import { defineAction, ActionError } from 'astro:actions';

export const server = {
  getUserSettings: defineAction({
    handler: async (_input, context) => {
      if (!context.locals.user) {
        throw new ActionError({ code: 'UNAUTHORIZED' });
      }
      return { /* data on success */ };
    }
  })
}
```

### 中间件中的拦截器 actions

<p><Since v="5.0.0" /></p>

Astro 建议从 action handler 中授权用户会话，以遵从每个 action 的权限级别和速率限制。但是，你也可以从中间件中拦截所有 action 请求（或一组 action 请求）。

使用 `getActionContext()` 函数从你的中间件中检索有关任何传入 action 请求的信息。这包括 action 名称以及该 action 是否是使用客户端远程过程调用（RPC）函数（例如 `actions.blog.like()`）或 HTML 表单调用的。

以下示例拒绝所有没有有效会话令牌的 action 请求。如果检查失败，将返回一个 “Forbidden” 响应。注意：此方法确保只有在会话存在时才能访问 action，但不是安全授权的替代品。

```ts title="src/middleware.ts"
import { defineMiddleware } from 'astro:middleware';
import { getActionContext } from 'astro:actions';

export const onRequest = defineMiddleware(async (context, next) => {
  const { action } = getActionContext(context);
  // 检查该 action 是否是从客户端函数调用的
  if (action?.calledFrom === 'rpc') {
    // 如果是，检查用户 session token
    if (!context.cookies.has('user-session')) {
      return new Response('Forbidden', { status: 403 });
    }
  }
  
  context.cookies.set('user-session', /* session token */);
  return next();
});
```

## 从 Astro 组件和服务器端点调用 action

你可以在 Astro 组件脚本中使用 `Astro.callAction()` 包装器直接调用 action（或者在使用 [服务器端点](/zh-cn/guides/endpoints/#服务器端点api-路由)时，使用 `context.callAction()`）。这常用于在其他服务端代码中复用你的 actions 逻辑。

将 action 作为第一个参数传递，并将任何输入参数作为第二个参数传递。这将返回你在客户端上调用 action 时，所收到的相同的 `data` 和 `error` 对象：

```astro title="src/pages/products.astro" {6}
---
import { actions } from 'astro:actions';

const searchQuery = Astro.url.searchParams.get('search');
if (searchQuery) {
  const { data, error } = await Astro.callAction(actions.findProduct, { query: searchQuery });
  // 处理结果
}
---
```
